#!/usr/bin/env python
from threading import RLock, Event

from xtquant import xtconstant
from xtquant.xttype import StockAccount
from xtquant.xttrader import XtQuantTrader
from xtquant import xtdata
from backtrader import Order
from pathlib import Path

from gj.ext.EClientErrors import EClientErrors
from gj.ext.Util import Util
from gj.ext.TraderCallback import TraderCallback
from gj.ext.Contract import Contract
from gj.lib.overloading import overloaded
from gj.lib import synchronized
from gj.lib import logger
import queue
import datetime
from collections import namedtuple
from pathlib import Path

_locks = {}
mlock = RLock()


class EClientSocket(object):

    # m_anyWrapper = None  # msg handler
    # m_connected = bool()  # true if we are connected

    def wrapper(self):
        return self.m_anyWrapper

    def __init__(self, anyWrapper):
        self.m_anyWrapper = anyWrapper
        self.m_connected = False
        self.xttrader = None
        self.logger = logger.logger()
        self.m_subscribe_quotes = {}
        self.m_subscribe_accounts = set()
        # backtrader的exectype与xtquant的price_type的映射,本不应引入backtrader的引用，为了方便这里就这样写了
        self.m_price_type_mapping = {Order.Market: xtconstant.LATEST_PRICE,
                                     Order.Limit: xtconstant.FIX_PRICE,
                                     Order.Close: xtconstant.LATEST_PRICE,
                                     }

    def isConnected(self):
        # self.logger.debug("isConnected: %s" % self.m_connected)
        return self.m_connected

    # @overloaded
    @synchronized(mlock)
    def eConnect(self, host, port, session_id, path):
        """连接国金证券的miniQMT
        """
        #  already connected?
        host = self.checkConnected(host)
        if host is None:
            return
        #  建立连接
        try:

            xtdata.connect(ip=host, port=port)
            if path is None:
                path = str(Path(xtdata.get_data_dir()).parent)

            self.xttrader = xttrader = XtQuantTrader(path, session_id)
            xttrader.register_callback(TraderCallback(self))
            xttrader.start()
            connect_result = xttrader.connect()
            if connect_result == 0:
                self.m_connected = True
                # self.reqSubscribeAccount(account)
            self.checkConnected(host)
        except Exception:
            self.eDisconnect()
            self.connectionError()

    def connectionError(self):
        self.m_anyWrapper.error(EClientErrors.NO_VALID_ID, EClientErrors.CONNECT_FAIL.code(
        ), EClientErrors.CONNECT_FAIL.msg())

    def checkConnected(self, host):
        if self.m_connected:
            self.m_anyWrapper.error(EClientErrors.NO_VALID_ID, EClientErrors.ALREADY_CONNECTED.code(
            ), EClientErrors.ALREADY_CONNECTED.msg())
            return None
        if self.isNull(host):
            host = "127.0.0.1"
        return host

    @synchronized(mlock)
    def eDisconnect(self):
        self.m_connected = False
        # 逐个取出m_subscribe_accounts中的account，取消订阅
        if self.m_subscribe_accounts:
            for account in self.m_subscribe_accounts:
                self.xttrader.unsubscribe(account)
            self.m_subscribe_accounts.clear()
        if self.xttrader:
            self.xttrader.stop()

    #  @deprecated, never called.

    @overloaded
    @synchronized(mlock)
    def error(self, err):
        self.m_anyWrapper.error(err)

    @synchronized(mlock)
    @error.register(object, int, int, str)
    def error_0(self, id, errorCode, errorMsg):
        self.m_anyWrapper.error(id, errorCode, errorMsg)

    @error.register(object, int, EClientErrors.CodeMsgPair, str)
    def error_1(self, id, pair, tail):
        self.error(id, pair.code(), pair.msg() +
                   tail)  # pylint: disable= too-many-function-args

    def close(self):
        self.eDisconnect()
        self.wrapper().connectionClosed()

    @classmethod
    def is_(cls, strval):
        #  return true if the string is not empty
        return strval is not None and len(strval) > 0

    @classmethod
    def isNull(cls, strval):
        #  return true if the string is null or empty
        return not cls.is_(strval)

    @classmethod
    def IsEmpty(cls, strval):
        return Util.StringIsEmpty(strval)

    def notConnected(self):
        self.error(EClientErrors.NO_VALID_ID, EClientErrors.NOT_CONNECTED,
                   "")  # pylint: disable= too-many-function-args

    # =================================================

    @synchronized(mlock)
    def reqContract(self, reqId, stock_code, is_completed=False):
        #  not connected?
        if not self.m_connected:
            self.notConnected()
            return False
        ins_detail = xtdata.get_instrument_detail(stock_code, is_completed)
        if not ins_detail:
            return False
        contract = Contract(**ins_detail)
        if ins_type := xtdata.get_instrument_type(stock_code):
            true_keys = [key for key, value in ins_type.items()
                         if value is True]
            if true_keys:
                contract.instrument_type = true_keys[0]
        self.m_anyWrapper.contractData(reqId, contract)
        return True

    @synchronized(mlock)
    def reqDownLoadData(self, stock_list, period='1d', start_time='', end_time='',
                        callback=None, incrementally=None):
        xtdata.download_history_data2(stock_list=stock_list, period=period,
                                      start_time=start_time, end_time=end_time,
                                      callback=callback, incrementally=incrementally)

    @synchronized(mlock)
    def reqSubscribeQuote(self, reqId, reqId2, stock_code, period='1d', start_time='', end_time='',
                          count=0, historical=True, download=False, callback=None,):
        filldatas = {}
        end_time = end_time if end_time else datetime.datetime.now().strftime('%Y%m%d%H:%M:%S')
        if not historical:
            def on_quote_data(datas):
                """
                回调函数，用于处理行情数据
                回调参数datas格式为 { stock_code : [data1, data2, ...] }
                """
                # if period == 'tick':
                #     for row in datas[stock_code]:
                #         # row为字典类型，将其转换为命名元组
                #         row = namedtuple('QuoteData', row.keys())(*row.values())
                #         self.m_anyWrapper.tickData(reqId, row)
                # else:
                for row in datas[stock_code]:
                    # row为字典类型，将其转换为命名元组
                    row = namedtuple('QuoteData', row.keys())(*row.values())
                    self.m_anyWrapper.quoteData(reqId2, row)
            if callback is None:
                callback = on_quote_data
            # count = 0   订阅最新数据，即实时返回的kline数据
            # count = -1  订阅所有数据，自动下载历史数据并合并订阅数据
            # count >0,period为tick时，返回的数据似乎有bug，多出前一日同样条数的数据
            self.m_subscribe_quotes[reqId] = xtdata.subscribe_quote(
                stock_code, period, start_time, end_time, count, callback)

        if download:
            xtdata.download_history_data(
                stock_code, period, start_time, end_time)
        # count =0或-1 返回所有数据
        # count > 0 返回最近count条数据
        # 这里的数据是已下载的历史数据或上面订阅的数据，如果无历史数据且订阅数据count=0且没有新数据到达
        # ，返回空数据
        # 如果period为tick，subscriibe_quote的count为0，get_market_data_ex始终返回空数据
        filldatas = xtdata.get_market_data_ex([], [stock_code], period,
                                              start_time, end_time,
                                              fill_data=False)
        # if period == 'tick':
        #     for row in filldatas[stock_code].itertuples():
        #         self.m_anyWrapper.tickData(reqId, row)
        # else:
        for row in filldatas[stock_code].itertuples():
            self.m_anyWrapper.historicalData(reqId, row)

        self.m_anyWrapper.historicalData(reqId, 'finished')
        return True

    @synchronized(mlock)
    def cancelSubscribeQuote(self, reqId):
        if reqId in self.m_subscribe_quotes:
            xtdata.unsubscribe_quote(self.m_subscribe_quotes[reqId])
            del self.m_subscribe_quotes[reqId]

    """帐号管理部分"""
    @synchronized(mlock)
    def reqManagedAccts(self):
        return self.xttrader.query_account_infos()

    # 订阅帐号

    @synchronized(mlock)
    def reqSubscribeAccount(self, account=None):
        if account is None:
            account_infos = self.xttrader.query_account_infos()
            for account_info in account_infos:
                if account_info.account_type == xtconstant.SECURITY_ACCOUNT \
                        and account_info.login_status == xtconstant.ACCOUNT_STATUS_OK:
                    account = account_info.account_id
                    break
            if account is None:
                return False
        # 删除帐号两边的空格
        account = account.strip()
        if self.xttrader.subscribe(StockAccount(account)) == 0:
            self.m_subscribe_accounts.add(account)
            print(f"订阅帐号{account}成功")
            self.error(EClientErrors.NO_VALID_ID, EClientErrors.ALREADY_SUBSCRIBED,
                   account)  # pylint: disable= too-many-function-args

    # 反订阅帐号方法
    @synchronized(mlock)
    def cancelSubscribeAccount(self, account):
        # 删除帐号两边的空格
        account = account.strip()
        # 逐个取出m_subscribe_accounts中的account，取消订阅
        if self.m_subscribe_accounts:
            if account in self.m_subscribe_accounts:
                self.xttrader.unsubscribe(account)
        self.error(EClientErrors.NO_VALID_ID,
                   EClientErrors.CANCEL_SUBSCRIBED, account)

    """交易处理部分"""
    @synchronized(mlock)
    def reqAccountAsset(self, account):
        """资产查询
        查询指定帐号的资产信息
        参数：
            account(str)：帐号
        返回值：
            XtAsset|None:该账号对应的资产对象XtAsset或者None
        """
        account = StockAccount(account)
        return self.xttrader.query_stock_asset(account)

    @synchronized(mlock)
    def reqQueryOrder(self, account):
        """订单查询
        查询指定帐号的订单信息
        参数：
            account(str)：帐号
        返回值：
            list[XtOrder]|None:返回指定帐号的订单信息
        """
        account = StockAccount(account)
        return self.xttrader.query_stock_orders(account)

    @synchronized(mlock)
    def reqPositions(self, account):
        """持仓查询
        查询指定帐号的持仓信息
        参数：
            account(str)：帐号
        返回值：
            list[XtPosition]|None:返回指定帐号的持仓信息
        """
        account = StockAccount(account)
        return self.xttrader.query_stock_positions(account)

    @synchronized(mlock)
    def reqTrades(self, account):
        """成交查询
        查询资金账号对应的当日所有成交
        参数：
            account(str)：帐号
        返回值：
            list[XtTrade]|None:该账号对应的当日所有成交对象XtTrade组成的list或者None
        """
        account = StockAccount(account)
        return self.xttrader.query_stock_trades(account)

    @synchronized(mlock)
    def reqOrderType(self, trade_type):
        return xtconstant.STOCK_BUY if trade_type == 'BUY' else xtconstant.STOCK_SELL

    @synchronized(mlock)
    def reqTrade(self, account, stock_code, order_type, order_volume, price_type, price, strategy_name, order_remark):
        """交易请求"""
        account = StockAccount(account)
        return self.xttrader.order_stock(account, stock_code, order_type, order_volume, price_type, price, strategy_name, order_remark)

    @synchronized(mlock)
    def reqPriceType(self, exectype):
        return self.m_price_type_mapping.get(exectype, xtconstant.LATEST_PRICE)

    @synchronized(mlock)
    def cancelOrder(self, account, order_id):
        account = StockAccount(account)
        return self.xttrader.cancel_order_stock(account, order_id)
